/**
 * 
 */
package qy.course.cse494.web;

import java.io.FileWriter;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;

import qy.course.cse494.ir.DocumentHits;
import qy.course.cse494.ir.DocumentIndex;
import qy.course.cse494.ir.DocumentQuery;
import qy.course.cse494.ir.DocumentSearch;

/**
 * @author qyan
 *
 */
public class WebPageRanker
{
	WebLinksExtractor _web_links_extractor;
	
	DocumentHits _rank_page_hits = null;
	
	int _top_k = -1;
	int _max_iterator = 1000;
	float _converged_epislon = 0.0001f; // epsilon
	float _dumping_factor = 1f; //??
	float _rel_impotance_weight = 0.8f;
	
	Hashtable<Integer, Float> _page_weight_list = new Hashtable<Integer, Float>(); 
	
	/**
	 * @param _web_links_extractor
	 */
	public WebPageRanker(WebLinksExtractor _web_links_extractor)
	{
		this._web_links_extractor = _web_links_extractor;
		_init();
	}
	
	/**
	 * Initiate members in the class
	 */
	private void _init()
	{
		Set<Integer> pages_set = _web_links_extractor.get_web_pages_set();
		int pages_num = pages_set.size();
		for(Integer cur_page_id : pages_set)
		{
			float weight = 1.0f / pages_num;
			_page_weight_list.put(cur_page_id, weight);
		}
		//
		_rank_page_hits = new DocumentHits(_top_k);
	}
	
	public void compute()
	{
		float distance = 0f;
		int iteration_count = 0;
		
		float max_weight = 0;
		
		do{
			++iteration_count;
			
			//System.out.print("iteration:" + iteration_count + " with difference ::" + distance + "\r");
			
			Hashtable<Integer, Float> cur_page_weight_list = new Hashtable<Integer, Float>();
			//
			for(Integer cur_page_id : _page_weight_list.keySet())
			{
				Vector<Integer> page_links_list = _web_links_extractor.LinksTo(cur_page_id);
				int links_num = page_links_list.size(); 
				for(Integer cur_link_id : page_links_list)
				{
					float weight = _page_weight_list.get(cur_page_id)/links_num;
					if(cur_page_weight_list.containsKey(cur_link_id))
					{
						weight += cur_page_weight_list.get(cur_link_id);
					}
					//System.out.println(cur_link_id+" : "+weight);
					//
					cur_page_weight_list.put(cur_link_id, weight);
				}
			}
			
			//System.out.println("page num:"+cur_page_weight_list.size() + " of "+_page_weight_list.size());
			
			//
			float weight_sum = 0f;
			int pages_num = _page_weight_list.size();
			for(Integer cur_page_id : _page_weight_list.keySet())
			{
				//?????
				float weight = (1-_dumping_factor)/pages_num;
				if(cur_page_weight_list.containsKey(cur_page_id))
				{
					weight += cur_page_weight_list.get(cur_page_id) * _dumping_factor;
				}
				cur_page_weight_list.put(cur_page_id, weight);
				//
				weight_sum += weight;
			}
			
			//System.out.println("weight sum:"+weight_sum);
			
			///////////////////////////////////////////////////////
			// 
			distance = 0;
			max_weight = 0;
			//System.out.println("Sum of weight is:" + _sum_page_weights);
			for(Integer cur_page_id : _page_weight_list.keySet())
			{
				float cur_weight = cur_page_weight_list.get(cur_page_id)/weight_sum;
				//
				float dist = cur_weight - _page_weight_list.get(cur_page_id);
				distance += dist * dist;
				
				_page_weight_list.put(cur_page_id, cur_weight);
				if(cur_weight > max_weight)
				{
					max_weight = cur_weight;
				}
			}
			
			distance = (float) Math.sqrt(distance);
			
		}while(distance > _converged_epislon && iteration_count < _max_iterator);
		
		//
		System.out.println("Iterations:" + iteration_count);
		System.out.println("Distance  :" + distance);
		
		// Normalize the results
		for(Integer cur_page_id : _page_weight_list.keySet())
		{
			float cur_weight = _page_weight_list.get(cur_page_id);
			_rank_page_hits.add_hit(cur_page_id, cur_weight/max_weight);
		}
		
	}
	
	/**
	 * Search
	 * @param hits
	 * @param index
	 * @return
	 */
	public DocumentHits search(DocumentHits hits, DocumentIndex index)
	{
		DocumentHits ret = new DocumentHits();
		
		float max_weight_page = 0;
		float max_weight_tf = 0;
		int length = hits.length();
		for(int i=0; i<length; ++i)
		{
			int cur_doc_code = index.get_doc_name(hits.get_doc_id_ranked_at(i)).hashCode();
			float page_weight = _page_weight_list.get(cur_doc_code);
			if(page_weight > max_weight_page)
			{
				max_weight_page = page_weight;
			}
			if(hits.get_weight_ranked_at(i) > max_weight_tf)
			{
				max_weight_tf = (float) hits.get_weight_ranked_at(i);
			}
		}
		
		//
		for(int i=0; i<length; ++i)
		{
			int cur_doc_code = index.get_doc_name(hits.get_doc_id_ranked_at(i)).hashCode();
			float page_weight = _page_weight_list.get(cur_doc_code)/max_weight_page;
			float tf_weight = (float) hits.get_weight_ranked_at(i)/max_weight_tf;
			//
			ret.add_hit(cur_doc_code, 
					_rel_impotance_weight*page_weight+(1-_rel_impotance_weight)*tf_weight);
		}
		
		return ret;
	}
	
	/**
	 * Export the page ranks into one file
	 * @param file_name
	 */
	public void export(final String file_name)
	{
		try 
		{
			FileWriter fpw = new FileWriter(file_name);
			//
			for(Integer cur_page_id : _page_weight_list.keySet())
			{
				float cur_weight = _page_weight_list.get(cur_page_id);
				fpw.write(cur_page_id+"\n");
				fpw.write(cur_weight+"\n");
				fpw.flush();
			}
			
			fpw.close();
			
		}catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
	
	public static void stat()
	{
		DocumentIndex di = new DocumentIndex("data/index4result3", true);
		DocumentSearch searcher = new DocumentSearch(di);
		WebLinksExtractor web_links_extractor = new WebLinksExtractor("data/Hashedlinks4");
		//
		String query_str = "src";
		DocumentQuery query = new DocumentQuery(query_str);
		DocumentHits hits = searcher.search(query, 10);
		WebPageRanker wpr = new WebPageRanker(web_links_extractor);
		wpr.compute();
		
		for(int i=0;i<hits.length(); ++i)
		{
			System.out.println(i + " : "+hits.get_weight_ranked_at(i)+" : " 
					+ hits.get_doc_id_ranked_at(i) + " :: "
					+ di.get_doc_name(hits.get_doc_id_ranked_at(i)));
		}
		
		float[] weight_list = {0, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f};
		
		for(int i=0; i<weight_list.length; ++i)
		{
			wpr.set_rel_impotance_weight(weight_list[i]);
			DocumentHits page_hits = wpr.search(hits, di);
			//
			System.out.println("************");
			for(int j=0;j<page_hits.length(); ++j)
			{
				System.out.println(j + " : "+page_hits.get_weight_ranked_at(j)+" : " 
						+ page_hits.get_doc_id_ranked_at(j) + " :: "
						+ di.get_doc_name_from_hashcode(page_hits.get_doc_id_ranked_at(j)));
			}	
		}
		
	}
	
	/**
	 * Main entry, for debugging the class
	 * @param args
	 */
	public static void main(String[] args)
	{
//		WebPageRanker.stat();
		WebLinksExtractor web_links_extractor = new WebLinksExtractor("data/Hashedlinks4");
		WebPageRanker wpr = new WebPageRanker(web_links_extractor);
		wpr.compute();
//		wpr.export("data/page_rand4");
//		System.out.println(wpr.get_rank_page_hits());
	}

	/**
	 * @return the _rank_page_hits
	 */
	public DocumentHits get_rank_page_hits()
	{
		return _rank_page_hits;
	}

	/**
	 * @return the _rel_impotance_weight
	 */
	public float get_rel_impotance_weight()
	{
		return _rel_impotance_weight;
	}

	/**
	 * @param _rel_impotance_weight the _rel_impotance_weight to set
	 */
	public void set_rel_impotance_weight(float _rel_impotance_weight)
	{
		this._rel_impotance_weight = _rel_impotance_weight;
	}
}
